<?php

namespace VueRender\Render;


use Illuminate\Support\Facades\Storage;
use VueRender\Contracts\Support\Renderable;
use VueRender\Schema;

/**
 * sfc 构建
 */
class Build implements Renderable
{
    protected string $filepath = "";
    protected string $filename;
    protected string $cachePath; //缓存路径
    protected string $cacheMd5;// 缓存内容MD5
    protected string $template;
    protected string $script;
    protected array $styles = [];
    protected string $cacheFile;

    protected array $states = [];
    protected array $refs = [];
    protected array $methods = [];
    protected array $computes = [];
    protected array $watches = [];
    protected array $lifeCycles = [];
    protected array $setupScripts = [];

    /**
     * @param string $filename
     * @param mixed $schemas
     * @param string $filepath
     */
    public function __construct(string $filename,mixed $schemas, string $filepath = "")
    {
        $this->filename = $filename;
        $this->filepath = $filepath;
        $this->setCacheFile();
        $templates = [];
        if (is_array($schemas)){
            foreach ($schemas as $schema){
                if ($schema instanceof Schema){
                    $templates[] = $schema->render()->getElement();
                    $this->mergeSchema($schema);
                }else{
                    $templates[] = $schema;
                }
            }
        }elseif ($schemas instanceof Schema){
            $templates[] = $schemas->render()->getElement();
            $this->mergeSchema($schemas);
        }else{
            $templates[] = $schemas;
        }
        $this->template = implode("\n",$templates);
        $this->script = "";
//        $this->style = "";
//        $this->script = $script;
//        $this->style = $style;
        return $this;
    }

    /**
     * @param Schema $schema
     * @return $this
     */
    protected function mergeSchema(Schema $schema):static
    {
        $this->refs = array_unique(array_merge($this->refs,$schema->getRefs()));
        $this->states = array_unique(array_merge($this->states,$schema->getStates()));
        $this->methods = array_unique(array_merge($this->methods,$schema->getMethods()));
        return $this;
    }

    /**
     * @param string $filename
     * @param mixed $schemas
     * @param string $filepath
     * @return Build
     */
    public static function make(string $filename,mixed $schemas, string $filepath = ""): Build
    {
        return new self($filename,$schemas, $filepath);
    }

    /**
     * @return string
     */
    public function getFilename():string
    {
        return $this->filename;
    }

    /**
     * @param string $filename
     * @return $this
     */
    public function setFilename(string $filename):static
    {
        $this->filename = $filename;
        return $this;
    }

    /**
     * @return string
     */
    public function getFilepath(): string
    {
        return $this->filepath;
    }

    /**
     * @param string $filepath
     * @return $this
     */
    public function setFilepath(string $filepath):static
    {
        $this->filepath = $filepath;
        return $this;
    }

    /**
     * @return string
     */
    public function getCachePath():string
    {
        return $this->cachePath;
    }

    /**
     * @return $this
     */
    protected function setCacheFile():static
    {
        if ($this->filepath){
            $this->cachePath = "vue/".trim($this->filepath,"/")."/".md5(rtrim($this->filename,".vue")).".vue";
        }else{
            $this->cachePath = "vue/".md5(rtrim($this->filename,".vue")).".vue";
        }
        return $this;
    }

    /**
     * @return string
     */
    public function getCacheFile():string
    {
        if (!$this->cacheFile){
            $this->render();
        }
        return $this->cacheFile;
    }

    /**
     * @return string
     */
    public function getCacheMd5():string
    {
        if (!$this->cacheMd5){
            $this->render();
        }
        return $this->cacheMd5;
    }


    /**
     * @param string $style
     * @return $this
     */
    public function style(string $style):static
    {
        $this->styles[] = $style;
        return $this;
    }

    /**
     * @param string $setupJs
     * @return $this
     */
    public function setup(string $setupJs):static
    {
        $this->setupScripts[] = $setupJs;
        return $this;
    }

    /**
     * @param $key
     * @param $value
     * @return $this
     */
    public function state($key,$value):static
    {
        $this->states[$key] = $value;
        return $this;
    }

    /**
     * 设置ref
     * @param $key
     * @param mixed|null $value
     * @param bool $isDomRef
     * @return $this
     */
    public function ref($key, mixed $value = null, bool $isDomRef = false):static
    {
        if (!$isDomRef) {
            if ($value instanceof Build) {
                $value = $value->render();
            }
        }
        $this->refs[$key] = $value;
        return $this;
    }

    /**
     * @param array $data
     * @param int $options
     * @return string
     */
    protected function getJson(array $data, int $options = JSON_UNESCAPED_UNICODE) {
        $jsonData = json_encode($data,$options);
        $sources = explode(",",$jsonData);
        $data = array_map(function ($value){
            return preg_replace_callback('/"(.*?)":/is',function($matches) {
                $match = $matches[1];
                return is_string($match) ? $match.":" : $match;
            },$value);
        },$sources);
        return implode(",",$data);
    }

    /**
     * @return string
     */
    public function render():string
    {
        $this->setCacheFile();
        $content = $this->toHtml();
        if (Storage::has($this->cachePath)){
            $cache = Storage::read($this->cachePath);
            if (md5($cache)!=md5($content)){
                Storage::put($this->cachePath, $content);
            }else{
                $this->clearOverTimeFile(function ($path) use ($content) {
                    if ($path==$this->cachePath){
                        Storage::put($this->cachePath, $content);
                    }
                });
            }
        }else{
            Storage::put($this->cachePath, $content);
        }
        $this->cacheMd5 = md5($content);
        $this->cacheFile = $content;
        return $content;
    }

    /**
     * 清除超过1天的过期文件
     * @param ?\Closure $callback
     * @return $this
     */
    public function clearOverTimeFile(\Closure $callback = null):static
    {
        if ($this->filepath){
            $dir = "vue/".trim($this->filepath,"/");
        }else{
            $dir = "vue";
        }
        $files = Storage::files($dir);
        if ($files){
            foreach ($files as $path){
                if (str_contains($path,".vue")){
                    $time = filemtime(Storage::path($path));
                    $nowTime = time();
                    if ($nowTime - $time> 24*3600){
                        Storage::delete($path);
                        $callback!=null && $callback($path);
                    }
                }
            }
        }
        return $this;
    }

    protected function toHtml(): string
    {
        $vueImports = ["reactive","inject"];
        if ($this->refs){
            $vueImports[] = "ref";
        }
        $vueImport = implode(",",$vueImports);
        $scripts = [
            'import {'.$vueImport.'} from "vue"',
//            'import dateFns from "https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.min.js"',
        ];
//        dump($this->states);
        $states = $this->getJson($this->states)?:"{}";
        $scripts[] = "const tools = inject('APP_TOOLS')";
        $scripts[] = "const state = reactive($states)";

        foreach ($this->refs as $refKey=>$refValue){
            $scripts[] = "const $refKey = ref($refValue)";
        }
        $scripts = array_merge($scripts,$this->methods);

        if ($this->setupScripts){
            foreach ($this->setupScripts as $setupJs){
                $scripts[] = $setupJs;
            }
        }

        $tmp = 'aria-label="'.$this->filename.'" aria-description="'.$this->cachePath.'"';

        $script = implode("\n",$scripts);
        $style = implode("\n",$this->styles);

        $html = ["<template $tmp>",$this->template,"</template>","<script setup>",$script,"</script>","<style scoped>",$style,"</style>"];
        return implode("\n",$html);
    }
}
